package testrank;

import java.util.*;

import org.aspectj.lang.*;
import org.aspectj.lang.reflect.*;

import testrank.predictors.CallCountPredictor;



/**
 * @author Hagai Cibulski
 *
 */
public aspect TestRanking {
	
	private static final boolean USE_VALUE_PROPAGATION = true;
	private static final boolean SHOW_TEST_EXEC = true;
	private static final boolean VERBOSE = false;
	
	private String testSignature = "None";
	
	private List<Object> callValues = null;
	private String callerSignature;
	
	pointcut testExecution() : 
		execution(public void *.test*())
	&& ! within (testrank..*)
	&& ! within (junit..*)
	&& ! within (edu.smu.tspell.wordnet..*)
	;
	

	after(): testExecution() {
		final SourceLocation testLocation = thisJoinPointStaticPart.getSourceLocation();
		final StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[2];
		final String fileName = testLocation.getFileName();
		final int returnLineNumber = stackTraceElement.getLineNumber();
		final String returnLocation = fileName + ":" + returnLineNumber;		

		if (SHOW_TEST_EXEC) {
			System.out.println("]] "+testSignature+" @ "+ returnLocation);
		}
		
		TestManager.testLocator().end(testSignature, fileName, testLocation.getLine(), returnLineNumber);		
	}


	before(): testExecution() {
		testSignature = thisJoinPointStaticPart.getSignature().toString();
		
		SourceLocation testLocation = thisJoinPointStaticPart.getSourceLocation();
		if (SHOW_TEST_EXEC)
			System.out.println("\n\n[[ "+ testSignature +" @ "+ testLocation);

		TestManager.testLocator().begin(testSignature, testLocation.getFileName(), testLocation.getLine());
		TestManager.testLocator().putMethodLocation(testLocation, TestRankConstants.TEST_DIR);
	
		if (USE_VALUE_PROPAGATION) {
			TestManager.valuePropagationPredictor().resetLastReturnValues();
		}
	}

	pointcut callFromTest() : 
		(call(* *.*(..)) || call(new(..)))
	&&	withincode(public void *.test*())
	&& ! within (testrank..*)
	&& ! within (junit..*)
	&& ! within (edu.smu.tspell.wordnet..*)
	;
	
	Object around () : callFromTest() {
		if (USE_VALUE_PROPAGATION) {
			Object target = thisJoinPoint.getTarget();
			Object[] args = thisJoinPoint.getArgs();
			callValues = new ArrayList<Object>();
			if (target != null) {
				callValues.add(target);
			}
			callValues.addAll(argsList(args));
			
			Object callReturnValue = proceed();
			TestManager.valuePropagationPredictor().endCallFromTest(testSignature, callReturnValue);
	
			return callReturnValue;
		}
		else {
			return proceed();
		}
	}

	private List<Object> argsList(Object[] args) {
		return args == null ? Collections.emptyList() : Arrays.asList(args);
	}

	pointcut mutCallFilter() :
		//within(org.apache.commons.math..*) // TODO abstract
		within(org.apache.log4j..*) // TODO abstract
		&& ! call(public void *.test*())
		&& ! call(public void *.theEnd())
		&& cflowbelow(execution(public void *.test*()))// needed to avoid tracing suite() code
		&& ! within (testrank..*)
		&& ! within (edu.smu.tspell.wordnet..*)
		;

	pointcut methodUnderTestCall(Object _this) :
		(call(* *(..)) || call(new(..)))
		&& mutCallFilter() 
		&& this(_this);
	
	before(Object _this): methodUnderTestCall(_this) {
		callerSignature = thisJoinPointStaticPart.getSignature().toString();
	}

	
	pointcut mutFilter() :
		//within(org.apache.commons.math..*) // TODO abstract
		within(org.apache.log4j..*) // TODO abstract
		&& ! execution(public void *.test*())
		&& ! execution(public void *.theEnd())
		&& cflowbelow(execution(public void *.test*()))// needed to avoid tracing suite() code
		&& ! within (testrank..*)
		&& ! within (junit..*)
		&& ! within (edu.smu.tspell.wordnet..*)
		;
	

	pointcut methodUnderTestExecution() : 
	(execution(* *(..)) || execution(new(..)))
	&& mutFilter();


	// after precedes around - so that getStackTrace()[2] will give the correct returnLineNumber
	// TODO currently begin line is taken from thisJoinPoint while return line is taken from stack
	after(): methodUnderTestExecution() {
		if (thisJoinPointStaticPart == null)
			return;
		
		Signature sig = thisJoinPointStaticPart.getSignature();
		String methodSignature = sig.toString();

		// currently only stack depth execution count predictor uses end, i.e. to decrease depth
		TestManager.sdCountPredictor().end(methodSignature, testSignature);
		
		
		if (TestManager.locator().hasEnd(methodSignature)) {
			//System.out.println(".. method already traced: " + methodSignature);
			return;
		}

		SourceLocation methodLocation = thisJoinPointStaticPart.getSourceLocation();
		String fileName = methodLocation.getFileName();
		StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[2];
		int returnLineNumber = stackTraceElement.getLineNumber();
		String returnLocation = fileName + ":" + returnLineNumber;
		if (VERBOSE)
			System.out.println(">> "+methodSignature+" @ "+ returnLocation);
	
		int beginLineNumber = methodLocation.getLine();

		if (returnLineNumber < beginLineNumber) {
			if (stackTraceElement.getMethodName().contains("init$_aroundBody0")) {
				// probably a default constructor provided by Java
				returnLineNumber = beginLineNumber;
			}
			else {
				throw new IllegalStateException("returnLineNumber=" + returnLineNumber + 
						" < beginLineNumber="+beginLineNumber);
			}
		}
		
		TestManager.locator().end(methodSignature, fileName, beginLineNumber, returnLineNumber);
	}


	// before precedes around - so that getStackTrace()[4,5] will give the correct listTrace for S2CountPredictor
	before(): methodUnderTestExecution() {

		if (thisJoinPointStaticPart == null)
			return;
			
		Signature sig = thisJoinPointStaticPart.getSignature();
		String methodSignature = sig.toString();
		SourceLocation methodLocation = thisJoinPointStaticPart.getSourceLocation();
		if (VERBOSE)
			System.out.println("<< "+ methodSignature +" @ "+ methodLocation);
		
		for (Predictor predictor : TestManager.predictors) {
			if (predictor instanceof CallCountPredictor) {
				((CallCountPredictor)predictor).setCallerSignature(callerSignature);
			}
				
			predictor.begin(methodSignature, testSignature);
		}

		if (USE_VALUE_PROPAGATION) {
			Object target = thisJoinPoint.getTarget();
			Object[] args = thisJoinPoint.getArgs();
			List<Object> executionValues = new ArrayList<Object>();
			if (target != null) {
				executionValues.add(target);
			}
			executionValues.addAll(argsList(args));
			TestManager.valuePropagationPredictor().begin(methodSignature, testSignature, executionValues, callValues);
		}

		int beginLineNumber = methodLocation.getLine();

		TestManager.locator().begin(methodSignature, methodLocation.getFileName(), beginLineNumber);
		TestManager.locator().putMethodLocation(methodLocation, TestRankConstants.SOURCE_DIR);
	}


	Object around() : methodUnderTestExecution() {
		Object executionReturnValue = proceed();
		if (USE_VALUE_PROPAGATION) {
			if (thisJoinPointStaticPart != null) {
				String methodSignature = thisJoinPointStaticPart.getSignature().toString();
				TestManager.valuePropagationPredictor().end(methodSignature, testSignature, executionReturnValue);
			}
		}
		return executionReturnValue;
	}
	

	pointcut theEnd() :
		execution(public void theEnd()); //log4j
		//execution(private void thisIsTheEnd()); //Math
	
	after() : theEnd() {
		end();
	}
	
	private void end() {
		System.out.println("\nThe End...");
		TestRank.preprocessAffinities();
	}
}
